/* 算法实现模板 */
import AlgorithmBase from "./AlgorithmBase";
import packages from "../../../package.json";
import ReactDOM from 'react-dom';
import React from 'react';
import Matrix from "../../common/Matrix";

import { classfy0, imgToVector } from "../../common/Common";
import HandWrittenView from "../../visual/HandWrittenView";
import HandWrittenBoard from "../../visual/HandWrittenBoard";

class KNNHandWrittenNums extends AlgorithmBase{
  constructor(){
    super('KNNHandWrittenNums');

    this.baseUrl  = `./${packages.homepage}data/ml-in-action/ch2`;
    this.testUrl  = `./${packages.homepage}data/ml-in-action/ch2/testDigits.json`;
    this.trainUrl = `./${packages.homepage}data/ml-in-action/ch2/trainingDigits.json`;

    this.trainDSUrls = [];
    this.testDSUrls  = [];

    // 图片信息
    this.imgWidth  = 0;
    this.imgHeight = 0;

    // 初始化本地数据集
    let localTrainDataset = {};
    let localTestDataset = {};
    for(let i = 0 ; i <= 9; i++){
      localTrainDataset[i] = [];
      localTestDataset[i] = [];
    }
    this.localTrainDataset = localTrainDataset;
    this.localTestDataset  = localTestDataset;
  }

  init(){ 
    this.clear();
    this.collect();
  }

  // 重置
  reset(){
    this.clear();

    this.trainDSUrls = [];
    this.testDSUrls  = [];
    this.trainDS     = undefined;
    this.testDS      = undefined;
    
    this.clearDataset();
  }

  loadTrainDS(url, args){
    const files = args.files;
    const current = args.current;
    const file = files[current].name;
    // 获取数组和数据集编号
    const num = parseInt(file.split('_')[0]);
    const id  = file.split('_')[1];

    this.localTrainDataset[num].push({"id":id, "url":url});

    this.trainDSUrls.push({label:num, url:url});

    if(current === files.length - 1){
      this.collectTips.innerText = '';

      ReactDOM.unmountComponentAtNode(this.collectDiv);

      ReactDOM.render(
        <React.StrictMode>
          <HandWrittenView 
            dataset = {this.localTrainDataset}
          />
        </React.StrictMode>,
        this.collectDiv
      );
    }
    else{
      this.collectTips.innerText = `正在加载数据${args.current}/${files.length}`;
    }
  }

  loadTestDS(url, args){
    const files = args.files;
    const current = args.current;
    const file = files[current].name;
    // 获取数组和数据集编号
    const num = parseInt(file.split('_')[0]);
    const id  = file.split('_')[1];

    this.localTestDataset[num].push({"id":id, "url":url});

    this.testDSUrls.push({label:num, url:url});

    if(current === files.length - 1){
      this.testLoadTips.innerText = '';

      ReactDOM.unmountComponentAtNode(this.testDiv);

      ReactDOM.render(
        <React.StrictMode>
          <HandWrittenView 
            dataset = {this.localTestDataset}
          />
        </React.StrictMode>,
        this.testDiv
      );
    }
    else{
      this.testLoadTips.innerText = `正在加载数据${args.current}/${files.length}`;
    }
  }

  // 初始化收集页面
  initCollectUI(){
    const collectTips = document.createElement('label');
    const collectDiv = document.createElement('div');

    this.domElementList.collect.appendChild(collectTips);
    this.domElementList.collect.appendChild(collectDiv);
    this.collectDiv  = collectDiv;
    this.collectTips = collectTips;
  }
  // 初始化准备页面
  initPrepareUI(){
    const btnPrepare       = document.createElement('button');
    btnPrepare.innerText   = '图片数据转向量';
    btnPrepare.addEventListener('click', this.prepare.bind(this));
    this.domElementList.prepare.appendChild(btnPrepare);

    const prepareLabel     = document.createElement('label');
    this.domElementList.prepare.appendChild(prepareLabel);
    this.prepareLabel      = prepareLabel;

    const prepareDIV      = document.createElement('div');
    this.domElementList.prepare.appendChild(prepareDIV);

    this.prepareDIV = prepareDIV;
  }

  // 初始化测试页面
  initTestUI(){
    const testLoadTips = document.createElement('label');
    const testDiv       = document.createElement('div');

    const btnLoadTest       = document.createElement('button');
    btnLoadTest.innerText   = '加载测试数据';
    btnLoadTest.addEventListener('click', this.loadTest.bind(this));

    const testLoadLabel      = document.createElement('label');
    testLoadLabel.innerText  = "";

    const resultLabel     = document.createElement('label');
    const resultDiv       = document.createElement('div');

    this.domElementList.test.innerHTML = '';
    this.domElementList.test.appendChild(testLoadTips);
    this.domElementList.test.appendChild(testDiv);
    this.domElementList.test.appendChild(btnLoadTest);
    this.domElementList.test.appendChild(testLoadLabel);
    
    this.domElementList.test.appendChild(resultLabel);
    this.domElementList.test.appendChild(resultDiv);

    this.testLoadLabel = testLoadLabel;
    this.resultLabel   = resultLabel;
    this.resultDiv     = resultDiv;
    this.testDiv       = testDiv;
    this.testLoadTips  = testLoadTips;
  }

  // 初始化使用页面
  initUsageUI(){
    const board = document.createElement('div');

    ReactDOM.render(
      <React.StrictMode>
        <HandWrittenBoard 
          process={this.usage.bind(this)}
        />
      </React.StrictMode>,
      board
    );

    const recgResult = document.createElement('label');
    recgResult.innerText = '识别结果:';

    this.domElementList.usage.appendChild(board);
    this.domElementList.usage.appendChild(recgResult);

    this.recgResult = recgResult;
  }

  clearDataset(){
    for(let i = 0 ; i <= 9; i++){
      this.localTrainDataset[i] = [];
      this.localTestDataset[i]  = [];
    }
  }

  // 收集数据
  collect(){
    // 清空数据集
    this.clearDataset();

    this.getFileUrls(this.trainUrl, (urls)=>{
      this.trainDSUrls = [];
      for(let key in urls){
        // 获取数组和数据集编号
        const num = parseInt(key.split('_')[0]);
        const id  = key.split('_')[1];
        const url = `${this.baseUrl}${urls[key]}`;
        this.localTrainDataset[num].push({"id":id, "url":url});

        this.trainDSUrls.push({label:num, url:url});
      }

      ReactDOM.unmountComponentAtNode(this.collectDiv);
      ReactDOM.render(
        <React.StrictMode>
          <HandWrittenView 
            dataset = {this.localTrainDataset}
          />
        </React.StrictMode>,
        this.collectDiv
      );
    });
  }

  // 准备数据
  prepare(){
    const trainDSUrls = this.trainDSUrls;
    if(!trainDSUrls) return;
    
    this.trainDS = {};
    this.trainDS.Mat = new Matrix.Matrix();
    this.trainDS.Labels = [];
    this.doFectch(0, trainDSUrls, this.trainDS,
      (index)=>{
      this.prepareLabel.innerText = `加载数据${index}/${trainDSUrls.length}`;
      },
      ()=>{
        this.prepareLabel.innerText = `加载完成`;
      }
    );
  }

  doFectch(index, datasetUrls, datasetStorage, loading, finished){
    if(index >= datasetUrls.length){
      finished();
      return;
    } 

    fetch(datasetUrls[index].url).then((response)=>{
      return response.text();
    }).then((txt)=>{
      loading(index);
      const imgVect = imgToVector(txt);
      datasetStorage.Mat.push(imgVect[0]);
      if(index === 0){
        this.imgWidth  = imgVect[1];
        this.imgHeight = imgVect[2];
      }
      datasetStorage.Labels.push(datasetUrls[index].label)
      return index + 1;
    }).then((next)=>{
      this.doFectch(next, datasetUrls, datasetStorage, loading, finished);
    })
  }

  getFileUrls(url, callback){
    fetch(url).then((response)=>{
      return response.json();
    }).then((json)=>{
     callback(json);
    })
  }

  loadTest(){
    this.testDS = {};
    this.testDS.Mat = new Matrix.Matrix();
    this.testDS.Labels = [];
    this.resultDiv.innerHTML = '';
    this.doFectch(0, this.testDSUrls, this.testDS,
      (index)=>{
        this.testLoadLabel.innerText = `加载数据${index}/${this.testDSUrls.length}`;
      },
      ()=>{
        this.testLoadLabel.innerText = `加载完成, 点击下放按钮测试.`;
        // 显示测试数据分组
        let i, il;
        for(i = 0, il = this.testDS.Mat.length; i < il; i+=100){
          const btn = document.createElement('button');
          btn.id = i;
          btn.value = `${i}-${i+100>this.testDS.Mat.length?this.testDS.Mat.length:i+100}`;
          btn.innerText = `${i}-${i+100>this.testDS.Mat.length?this.testDS.Mat.length:i+100}`;
          btn.addEventListener('click', this.test.bind(this));
          this.resultDiv.appendChild(btn);
        }
      }
    );
  }

  // 从服务器加载测试数据
  initTest(){
    this.clearDataset();
    this.getFileUrls(this.testUrl, (urls)=>{
      this.testDSUrls = [];
      for(let key in urls){
        // 获取数组和数据集编号
        const num = parseInt(key.split('_')[0]);
        const id  = key.split('_')[1];
        const url = `${this.baseUrl}${urls[key]}`;
        this.localTestDataset[num].push({"id":id, "url":url});

        this.testDSUrls.push({label:num, url:url});
      }

      ReactDOM.unmountComponentAtNode(this.testDiv);

      ReactDOM.render(
        <React.StrictMode>
          <HandWrittenView 
            dataset = {this.localTestDataset}
          />
        </React.StrictMode>,
        this.testDiv
      );
    });
  }
  // 载入测试数据
  // 初始化测试页面
  test(event){
    const range = event.target.value.split('-');
    const start = parseInt( range[0] );
    const end = parseInt( range[1] );
    this.doTest(start, end);
  }

  doTest(start, end){
    const testDS  = this.testDS;
    const trainDS = this.trainDS;
    if(!testDS || !trainDS){
      this.resultLabel.innerText=`数据未加载...`;
      return;
    } 

    let errCount = 0;//testDS.Mat.length
    for(let i=start; i < end; i++){
      const classifierResult = classfy0(testDS.Mat[i], trainDS.Mat, trainDS.Labels, 3);
      console.log(`测试:${i},实际值${testDS.Labels[i]},预测值${classifierResult}`);
      
      if( parseInt(testDS.Labels[i]) !== parseInt(classifierResult)){
        errCount ++;
      }
    }

    this.resultLabel.innerText=`错误率:${(errCount / (end - start + 1)).toFixed(6)}`;
  }

  // 使用算法
  usage(imgData){
    // console.log(imgData);
    const trainDS = this.trainDS;
    if(!trainDS){
      this.recgResult.innerText = '未加载训练数据.';
      return;
    }
    let testMat = new Matrix.Matrix();
    testMat.copyFrom(imgData);
    // console.log(testMat);
    const classifierResult = classfy0(testMat, trainDS.Mat, trainDS.Labels, 3);
    this.recgResult.innerText = `识别结果：${classifierResult}`;
  }
}


export default KNNHandWrittenNums;